package net.xxcxcxcx.xc_enhancedmob.entity;

import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.api.EnvironmentInterface;
import net.fabricmc.api.EnvironmentInterfaces;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.FlyingItemEntity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.MovementType;
import net.minecraft.entity.damage.ProjectileDamageSource;
import net.minecraft.entity.data.DataTracker;
import net.minecraft.entity.data.TrackedData;
import net.minecraft.entity.data.TrackedDataHandlerRegistry;
import net.minecraft.entity.projectile.ProjectileUtil;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.nbt.NbtList;
import net.minecraft.network.Packet;
import net.minecraft.particle.ParticleTypes;
import net.minecraft.sound.SoundCategory;
import net.minecraft.sound.SoundEvents;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.hit.EntityHitResult;
import net.minecraft.util.hit.HitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.RaycastContext;
import net.minecraft.world.World;
import net.xxcxcxcx.xc_enhancedmob.packet.CustomEntitySpawnPacket;

import java.util.List;
import java.util.OptionalInt;

import static net.xxcxcxcx.xc_enhancedmob.EnhancedMobMod.CustomEntityIdentifier;
import static net.xxcxcxcx.xc_enhancedmob.register.EntityRegister.SaveFireworkRocketType;

@EnvironmentInterfaces(value = {@EnvironmentInterface(value = EnvType.CLIENT, itf = FlyingItemEntity.class)})
public class SaveFireworkRocketEntity extends CustomProjectileEntity implements FlyingItemEntity {
    private static final TrackedData<ItemStack> ITEM = DataTracker.registerData(SaveFireworkRocketEntity.class, TrackedDataHandlerRegistry.ITEM_STACK);
    private static final TrackedData<OptionalInt> Target_ENTITY_ID = DataTracker.registerData(SaveFireworkRocketEntity.class, TrackedDataHandlerRegistry.FIREWORK_DATA);
    private static final TrackedData<Boolean> SHOT_AT_ANGLE = DataTracker.registerData(SaveFireworkRocketEntity.class, TrackedDataHandlerRegistry.BOOLEAN);
    public int attrange = 5, damage = 0;
    private int life;
    private int lifeTime;
    private LivingEntity target;

    public SaveFireworkRocketEntity(EntityType<? extends SaveFireworkRocketEntity> entityType, World world) {
        super(entityType, world);
    }

    public SaveFireworkRocketEntity(World world, double x, double y, double z, ItemStack stack) {
        this(SaveFireworkRocketType, world);
        this.life = 0;
        this.setPosition(x, y, z);
        int i = 1;
        if (!stack.isEmpty() && stack.hasTag()) {
            this.dataTracker.set(ITEM, stack.copy());
            i += stack.getOrCreateSubTag("Fireworks").getByte("Flight");
        }
        this.setVelocity(this.random.nextGaussian() * 0.001, 0.05, this.random.nextGaussian() * 0.001);
        this.lifeTime = 10 * i + this.random.nextInt(6) + this.random.nextInt(7);
    }

    public SaveFireworkRocketEntity(World world, double x, double y, double z, double sx, double sy, double sz) {
        this(world, x, y, z, new ItemStack(Items.FIREWORK_ROCKET));
        this.setVelocity(sx, sy, sz);
    }

    public SaveFireworkRocketEntity(World world, ItemStack stack, double x, double y, double z, boolean shotAtAngle) {
        this(world, x, y, z, stack);
        this.dataTracker.set(SHOT_AT_ANGLE, shotAtAngle);
    }

    public SaveFireworkRocketEntity(World world, ItemStack stack, Entity entity, double x, double y, double z, boolean shotAtAngle, Entity target) {
        this(world, stack, x, y, z, shotAtAngle);
        this.setOwner(entity);
        if (target instanceof LivingEntity) {
            this.dataTracker.set(Target_ENTITY_ID, OptionalInt.of(target.getEntityId()));
        }
    }

    @Override
    protected void initDataTracker() {
        this.dataTracker.startTracking(ITEM, ItemStack.EMPTY);
        this.dataTracker.startTracking(Target_ENTITY_ID, OptionalInt.empty());
        this.dataTracker.startTracking(SHOT_AT_ANGLE, false);
    }

    @Override
    @Environment(value = EnvType.CLIENT)
    public boolean shouldRender(double distance) {
        return distance < 4096.0;
    }

    @Override
    @Environment(value = EnvType.CLIENT)
    public boolean shouldRender(double cameraX, double cameraY, double cameraZ) {
        return super.shouldRender(cameraX, cameraY, cameraZ);
    }

    private boolean hasShotTarget() {
        return this.dataTracker.get(Target_ENTITY_ID).isPresent();
    }


    public boolean wasShotAtAngle() {
        return this.dataTracker.get(SHOT_AT_ANGLE);
    }

    //写入NBT
    @Override
    public void writeCustomDataToNbt(NbtCompound nbt) {
        super.writeCustomDataToNbt(nbt);
        nbt.putInt("Life", this.life);
        nbt.putInt("LifeTime", this.lifeTime);
        ItemStack itemStack = this.dataTracker.get(ITEM);
        if (!itemStack.isEmpty()) {
            nbt.put("FireworksItem", itemStack.writeNbt(new NbtCompound()));
        }
        nbt.putInt("attrange", attrange);
        nbt.putInt("damage", damage);
        nbt.putBoolean("ShotAtAngle", this.dataTracker.get(SHOT_AT_ANGLE));
        nbt.putInt("TargetEntityId", this.dataTracker.get(Target_ENTITY_ID).orElse(-1) + 1);
    }

    //读取NBT
    @Override
    public void readCustomDataFromNbt(NbtCompound nbt) {
        super.readCustomDataFromNbt(nbt);
        this.life = nbt.getInt("Life");
        this.lifeTime = nbt.getInt("LifeTime");
        ItemStack itemStack = ItemStack.fromNbt(nbt.getCompound("FireworksItem"));
        if (!itemStack.isEmpty()) {
            this.dataTracker.set(ITEM, itemStack);
        }
        this.attrange = nbt.getInt("attrange");
        this.damage = nbt.getInt("damage");
        if (nbt.contains("ShotAtAngle")) {
            this.dataTracker.set(SHOT_AT_ANGLE, nbt.getBoolean("ShotAtAngle"));
        }
        if (nbt.contains("TargetEntityId")) {
            this.dataTracker.set(Target_ENTITY_ID, nbt.getInt("TargetEntityId") == 0 ? OptionalInt.empty() : OptionalInt.of(nbt.getInt("TargetEntityId") - 1));
        }
    }

    //每秒处理
    @Override
    public void tick() {
        Vec3d vec3d;
        super.tick();
        if (this.hasShotTarget()) {
            if (this.target == null) {
                this.dataTracker.get(Target_ENTITY_ID).ifPresent(i -> {
                    Entity entity = this.world.getEntityById(i);
                    if (entity instanceof LivingEntity) {
                        this.target = (LivingEntity) entity;
                    }
                });
            }
        }
        vec3d = this.getVelocity();
        this.move(MovementType.SELF, vec3d);
        this.setVelocity(vec3d);
        HitResult hitResult = ProjectileUtil.getCollision(this, this::method_26958);
        if (!this.noClip) {
            this.onCollision(hitResult);
            this.velocityDirty = true;
        }
        this.method_26962();
        if (this.life == 0 && !this.isSilent()) {
            this.world.playSound(null, this.getX(), this.getY(), this.getZ(), SoundEvents.ENTITY_FIREWORK_ROCKET_LAUNCH, SoundCategory.AMBIENT, 3.0f, 1.0f);
        }
        ++this.life;
        if (this.world.isClient) {
            this.world.addParticle(ParticleTypes.FIREWORK, this.getX(), this.getY() - 0.3, this.getZ(), this.random.nextGaussian() * 0.05, -this.getVelocity().y * 0.5, this.random.nextGaussian() * 0.05);
        }
        if (!this.world.isClient && this.life > this.lifeTime) {
            this.explodeAndRemove();
        }
    }

    //爆炸和移除实体
    private void explodeAndRemove() {
        this.world.sendEntityStatus(this, (byte) 17);
        this.explode();
        this.remove();
    }

    //计算爆炸伤害
    private void explode() {
        NbtList nbtList;
        float f = 0.0f;
        ItemStack itemStack = this.dataTracker.get(ITEM);
        NbtCompound nbtCompound = itemStack.isEmpty() ? null : itemStack.getSubTag("Fireworks");
        nbtList = nbtCompound != null ? nbtCompound.getList("Explosions", 10) : null;
        if (nbtList != null && !nbtList.isEmpty()) {
            f = 5.0f + (float) (nbtList.size() * 2) + damage;
        }
        if (f > 0.0f) {
            Vec3d vec3d = this.getPos();
            List<LivingEntity> list = this.world.getNonSpectatingEntities(LivingEntity.class, this.getBoundingBox().expand(attrange));
            for (LivingEntity livingEntity : list) {
                if (this.squaredDistanceTo(livingEntity) > attrange * attrange) continue;
                boolean bl = false;
                for (int i = 0; i < 2; ++i) {
                    Vec3d vec3d2 = new Vec3d(livingEntity.getX(), livingEntity.getBodyY(0.5 * (double) i), livingEntity.getZ());
                    BlockHitResult hitResult = this.world.raycast(new RaycastContext(vec3d, vec3d2, RaycastContext.ShapeType.COLLIDER, RaycastContext.FluidHandling.NONE, this));
                    if (((HitResult) hitResult).getType() != HitResult.Type.MISS) continue;
                    bl = true;
                    break;
                }
                if (!bl) continue;
                float g = f * (float) Math.sqrt((attrange - (double) this.distanceTo(livingEntity)) / attrange);
                livingEntity.damage(new ProjectileDamageSource("savefireworks", this, this.getOwner()).setExplosive(), g);
            }
        }
    }

    //命中实体的处理
    @Override
    protected void onEntityHit(EntityHitResult entityHitResult) {
        super.onEntityHit(entityHitResult);
        if (this.world.isClient) {
            return;
        }
        this.explodeAndRemove();
    }

    //命中方块处理
    @Override
    protected void onBlockHit(BlockHitResult blockHitResult) {
        BlockPos blockPos = new BlockPos(blockHitResult.getBlockPos());
        this.world.getBlockState(blockPos).onEntityCollision(this.world, blockPos, this);
        if (!this.world.isClient() && this.hasExplosionEffects()) {
            this.explodeAndRemove();
        }
        super.onBlockHit(blockHitResult);
    }

    //是否有爆炸效果
    private boolean hasExplosionEffects() {
        ItemStack itemStack = this.dataTracker.get(ITEM);
        NbtCompound nbtCompound = itemStack.isEmpty() ? null : itemStack.getSubTag("Fireworks");
        NbtList nbtList = nbtCompound != null ? nbtCompound.getList("Explosions", 10) : null;
        return nbtList != null && !nbtList.isEmpty();
    }

    //触发爆炸效果
    @Override
    @Environment(value = EnvType.CLIENT)
    public void handleStatus(byte status) {
        if (status == 17 && this.world.isClient) {
            if (!this.hasExplosionEffects()) {
                for (int i = 0; i < this.random.nextInt(3) + 2; ++i) {
                    this.world.addParticle(ParticleTypes.POOF, this.getX(), this.getY(), this.getZ(), this.random.nextGaussian() * 0.05, 0.005, this.random.nextGaussian() * 0.05);
                }
            } else {
                ItemStack itemStack = this.dataTracker.get(ITEM);
                NbtCompound nbtCompound = itemStack.isEmpty() ? null : itemStack.getSubTag("Fireworks");
                Vec3d vec3d = this.getVelocity();
                this.world.addFireworkParticle(this.getX(), this.getY(), this.getZ(), vec3d.x, vec3d.y, vec3d.z, nbtCompound);
            }
        }
        super.handleStatus(status);
    }

    @Override
    @Environment(value = EnvType.CLIENT)
    public ItemStack getStack() {
        ItemStack itemStack = this.dataTracker.get(ITEM);
        return itemStack.isEmpty() ? new ItemStack(Items.FIREWORK_ROCKET) : itemStack;
    }

    @Override
    public boolean isAttackable() {
        return false;
    }

    @Override
    public Packet<?> createSpawnPacket() {
        return CustomEntitySpawnPacket.creat(this, CustomEntityIdentifier);
    }
}
